package stv5.managers;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import javax.swing.filechooser.FileSystemView;

import stv5.Episode;
import stv5.Request;
import stv5.Series;
import stv5.SeriesList;
import stv5.abstracted.AbstractManager;
import stv5.abstracted.EpisodeManager;

/**
 * An EpisodeManager that cannot stream, and provides access
 * 	to local files on the local computer only. This is useful
 * 	in simple situations where a user uses their computer
 * 	as primary means of watching shows. 
 * 
 * @author Daniel
 *
 */
public class LocalFileManager extends AbstractManager implements EpisodeManager {
	String folderInfo = "";
	private String[] localFolders;
	private HashMap<Integer, ArrayList<Episode>> episodes;
	private FilenameFilter myFileFilter;
	private FileFilter myFolderFilter;
	private SeriesList mySeries;
	
	private int guid=0;
	private int localFolderCount = 0;
	
	/**
	 * The only constructor. It is designed to have its
	 * 	argument passed directly from the commandline 
	 * 
	 * @param localfolders A string representing either
	 * 	a ";" separated list of root folders wherein
	 * 	the series' folders are located, or a filename
	 * 	which gives this list 
	 */
	public LocalFileManager(String folderInfo) {
		this.folderInfo = folderInfo;
		
		myFileFilter = new VideoFileFilter();
		myFolderFilter = new FolderFilter();
	}
		
	private void addLocalFolders(String...paths) {
		localFolders = new String[ paths.length ];
		localFolderCount = 0;
		
		for (String path : paths) 
			addLocalFolder( path );		
	}
	
	private void addLocalFolders(List<String> paths) {
		localFolders = new String[ paths.size() ];
		localFolderCount = 0;
		
		for (String path : paths) 
			addLocalFolder( path );		
	}
	
	/**
	 * Attempts to add "path" to the list of local folders;
	 * 	Ignores the path if invalid or could not be found
	 * 
	 * If path matches "@NAME/..", we assume it's a Windows
	 * 	system and attempt to find an external harddrive
	 * 	whose label matches NAME
	 * @param path
	 */
	private void addLocalFolder(String path) {
		if (path.charAt(0) == '@') {
			int end = path.indexOf( File.separatorChar );
			if (end < 0)
				return;
			
			String needle = path.substring(1, end);
			String actualPath = path.substring(end+1);
			
			File[] roots = File.listRoots();
			FileSystemView v = FileSystemView.getFileSystemView();
			String label;
			for (File f : roots) {
				label = v.getSystemDisplayName(f);
				if (label.startsWith(needle)) {
					localFolders[ localFolderCount ] = f.getAbsolutePath() + actualPath;
					break;
				}
			}
		} else {
			// just add it
			localFolders[ localFolderCount ] = path;
		}
			
		localFolderCount++;
	}

	@Override
	public List<Episode> getEpisodes(int seriesId) {
		return episodes.get(seriesId);
	}

	@Override
	public String getLocalForEpisode(Series s, Episode e) {
		return e.getLink();
	}

	@Override
	public void handleRequest(StringBuffer buff, Request request) {
		// no special requests to handle
	}

	@Override
	public void manageSeries(Series s) {	
		Series original = mySeries.getByName(s);
		if (original != null) {
			original.manageIfy(s);			
			
			ArrayList<Episode> list = new ArrayList<Episode>();
			File localPath = new File(original.getLocalPath());
			if (localPath.exists() && localPath.isDirectory()) {	
				
				for (File file : localPath.listFiles(myFileFilter)) 
					list.add(new Episode(file.getName(), file.getAbsolutePath()));
				
				Collections.sort(list);
				episodes.put(original.getId(), list);
				return;
			}
			// load episodes directly		
			episodes.put(original.getId(), list);
			
		} else mySeries.add(s);	
	}

	@Override
	public void reload() {
		episodes = new HashMap<Integer, ArrayList<Episode>>();
		
		System.out.print(" - Initializing Local-file Manager...");
		// load the local folders list
		if (folderInfo.contains(File.pathSeparator)) {
			// it's definitely a list
			String[] paths = folderInfo.split(File.pathSeparator);
			addLocalFolders( paths );
		} else {
			File cfgFile = new File(folderInfo);
			if (!cfgFile.exists() && folderInfo.charAt(0) != '@') {
				System.out.println(" - No folders provided; Failed.");
				System.err.println("---------- DEBUG ----------");
				System.err.println(" FolderInfo: " + folderInfo);
				System.err.println(" file = " + cfgFile);
				if (cfgFile != null) {
					System.err.println(" file.path = " + cfgFile.getAbsolutePath());
					System.err.println(" file.exists = " + cfgFile.exists());
					System.err.println(" file.canRead = " + cfgFile.canRead());
					System.err.println(" file.isDirectory = " + cfgFile.isDirectory());
				}
				System.err.println("---------- DEBUG ----------");
				System.exit(1);
			} 
				
			if (cfgFile.isFile()) {
				// it's file; read line by line
				ArrayList<String> folders = new ArrayList<String>();
				try {
					BufferedReader reader = new BufferedReader(
							new InputStreamReader(
							new DataInputStream(
									new FileInputStream(cfgFile.getCanonicalFile())
									))
							);
					String line;
					while ((line=reader.readLine()) != null)
						folders.add(line);
					
					addLocalFolders( folders );
				} catch (IOException e) {
					System.out.println(" - Could not read folders from file " 
							+ folderInfo);
					System.exit(1);
				}
			} else {
				// it's a single folder
				addLocalFolders( folderInfo );
			}
		} 
		
		mySeries = spiderFolders();
		System.out.println("Done.");		
	}

	@Override
	public int size() {
		return episodes.size();
	}

	@Override
	public SeriesList spiderFolders() {
		//return spiderFolders("");
		SeriesList dirs = new SeriesList();
		for (String fname : localFolders) 
			doSpiderFolders(fname, dirs);			
		
		return dirs;
	}

	@Override
	public SeriesList spiderFolders(String folderName) {
		SeriesList dirs = new SeriesList();
		doSpiderFolders(folderName, dirs);
		return dirs;
	}
	
	private void doSpiderFolders(String folderName, SeriesList list) {		
		File folder;
		File[] videos, subfolders;
		
		if (folderName == null || (folder = new File(folderName)) == null)
			return;
				
		videos = folder.listFiles(myFileFilter);
		if (videos != null && videos.length > 0) {
			String absPath = folder.getAbsolutePath();
			boolean keepFolder = true;
			for (String fname : localFolders) {
				if (absPath.equalsIgnoreCase(fname)) {
					keepFolder = false;
					break;
				}
				
				if (absPath.startsWith(fname)) {
					absPath = absPath.substring( fname.length() );
					break;
				}
			}
			if (keepFolder) {											
				//System.out.println("Adding video folder: " + folderName);
				String seriesName = getNameFromPath( absPath );
				Series news = new Series( seriesName, ""+(guid++));
				news.setLocalPath(folderName);
				list.add( news );				
			}
		}

		// recurse into subdirectories
		subfolders = folder.listFiles(myFolderFilter);
		if (subfolders != null && subfolders.length > 0) {
			for (File subdir : subfolders) 			
				doSpiderFolders(subdir.getAbsolutePath(), list);
		}
		
		return;	
	}
	
	private String getNameFromPath( String path ) {
		// because of windows silliness, we can't just split
		// the path like normal, lazy people :(
		int lastDirPos = path.lastIndexOf( File.separatorChar );
		String rawName = path.substring(lastDirPos + 1);		
		return AbstractManager.capitalizeWords(rawName);
	}
	
	private class VideoFileFilter implements FilenameFilter {
		@Override
		public boolean accept(File dir, String filename) {
			return (AbstractManager.isVideoFile(filename));			
		}


	}

	private class FolderFilter implements FileFilter {

		@Override
		public boolean accept(File file) {
			return file.isDirectory();
		}
		
	}
}
